#include "pch.hpp"
#include "System.hpp"

#include "ScriptComponent.hpp"




Engine::Entity::System::System(const EngineInfo& info,
								Video::Device::ptr videoDevice,
								Input::Device::ptr inputDevice,
								Script::System::ptr scriptSystem)
	: m_logger(info.serviceProvider->service<logger::stream>())

	, m_videoDevice(videoDevice)
	, m_inputDevice(inputDevice)
	, m_scriptSystem(scriptSystem)

	, m_cameraComponent(new CameraComponent(inputDevice, videoDevice))
{
	m_physicsSystem.setRequiresUpdate(true);
	m_positionSystem.setRequiresUpdate(false);
	m_cameraSystem.setRequiresUpdate(true);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Engine::Entity::System::~System()
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




QString Engine::Entity::System::name() const
{
	return "EntitySystem";
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Engine::Entity::Object* Engine::Entity::System::createEntity()
{
	auto entity = new Object(this);
	m_entities.insert(Object::ptr(entity));
	// Ref count will be at one, however we're also storing a reference in our container,
	// therefore the reference count must be 2 (one for the container and one for the returned
	// handle, that is given to angelscript).
	entity->AddRef();

	return entity;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Entity::System::destroyEntity(Object* entity)
{
	// We're adding one reference for the pointer we create
	// (We make a copy of the handle)
	entity->AddRef();
	auto tmp = Object::ptr(entity);

	auto i = m_entities.find(tmp);
	if(i == m_entities.end())
		return;

	m_entities.erase(i);
	detach_all(entity);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Engine::Entity::CameraComponent* Engine::Entity::System::cameraComponent() const
{
	m_cameraComponent->AddRef();
	return m_cameraComponent.get();
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Entity::System::update()
{
	// First, update the hardcoded systems
	m_physicsSystem.update();
	m_positionSystem.update();
	m_cameraSystem.update();

	// Then update the user defined systems
	for(auto i = m_userSystems.begin(); i != m_userSystems.end(); ++i)
	{
		i->second->update();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




bool Engine::Entity::System::attach(Object* entity, Component::ptr component)
{
	// Null handles will be silently ignored
	if(!entity || !component)
		return false;

	// Attaching a component to the entity it is already attached to is ignored
	Component::ID id = component->id();
	if(component->entity() == entity)
	{
		m_logger->log_info(QString("component (%1) is already attached to the given entity, ignoring attach").arg(id));
		return false;
	}


	// This component might require other components, so let's look if this
	// is the case and collect them all, if it is
	auto requiredComponents = component->requiredComponents();
	Component::map components;
	if(requiredComponents.size() > 0)
	{
		for(auto i = requiredComponents.begin(); i != requiredComponents.end(); ++i)
		{
			ComponentSystem& system = componentSystem(*i);
			auto component = system.component(entity);
			if(component != nullptr)
			{
				components.insert(std::make_pair(*i, component));
			}
			else
			{
				m_logger->log_error(QString("Unable to attach component (%1) to entity, required component (%2) is not attached to that entity (yet)").arg(id).arg(*i));
				return false;
			}
		}
	}


	// If the component is already attached to another entity, then we detach
	// it from said entity first
	if(component->isAttached())
		detach(component->entity(), component);


	// We have to delegate the call to the right component system.
	// Each system is defined by a unique id, so we look for the right one
	ComponentSystem& system = componentSystem(id);
	system.setRequiresUpdate(component->requiresUpdate());

	// And attach it...
	return system.attach(entity, component, components);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool Engine::Entity::System::detach(Object* entity, Component::ptr component)
{
	// Well if the user passed nullptrs then we ignore this call silenty
	if(!entity || !component || !component->isAttached())
		return false;

	// The entity should be the one from the component, otherwise
	// there is an error...
	if(component->entity() != entity)
	{
		m_logger->log_error(QString("Unable to detach component (%1) from entity: It's attached to another entity").arg(component->id()));
		return false;
	}

	// We have to delegate the call to the right component system.
	// Each system is defined by a unique id, so we look for the right one
	unsigned int id = component->id();
	ComponentSystem& system = componentSystem(id);

	return system.detach(entity);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Entity::System::detach_all(Object* entity)
{
	// In case the scripter forgot to detach components from
	// this entity, we will notfiy the appropriate systems ourselves
	if(entity->numComponents() != 0)
	{
		m_physicsSystem.detach(entity);
		m_positionSystem.detach(entity);

		// What do we do with the camera?!?
		m_cameraSystem.detach(entity);

		std::for_each(m_userSystems.begin(), m_userSystems.end(), [&](const ComponentSystems::value_type& system)
		{
			system.second->detach(entity);
		});
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Entity::System::destroyed(Object* entity)
{
	// This function is ONLY called from within the destructor of Entity::Object.
	// (Therefore the reference count is 0). However we require an object of type
	// Script::pointer<Entity::Object> in order to look in our map for the entity.
	// We simply "cheat" and increase the reference count by 2 so we can create a
	// pointer to erase it from the hash map. (We must increase it by 2, otherwise
	// the pointer would call delete again when going out of scope).
	entity->AddRef();
	entity->AddRef();
	auto tmp = Object::ptr(entity);

	auto i = m_entities.find(tmp);
	if(i == m_entities.end())
		return;

	m_entities.erase(i);

	detach_all(entity);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Engine::Entity::ComponentSystem& Engine::Entity::System::componentSystem(unsigned int id)
{
	switch(id)
	{
	// Hard coded systems
	case Components::Position: return m_positionSystem;
	case Components::Physics:  return m_physicsSystem;
	case Components::Camera:   return m_cameraSystem;
	// User defined systems
	default:
		{
			auto i = m_userSystems.find(id);
			if(i != m_userSystems.end())
			{
				return *i->second;
			}
			else
			{
				auto system = boost::make_shared<ComponentSystem>();
				m_userSystems.insert(std::make_pair(id, system));
				return *system;
			}
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Engine::Script::System::ptr Engine::Entity::System::scriptSystem() const
{
	return m_scriptSystem;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Entity::System::registerType(Script::System::ptr engine)
{
	Component::registerType(engine);
	PositionComponent::registerType(engine);
	CameraComponent::registerType(engine);
	ScriptComponent::registerType(engine);
	Object::registerType(engine);

	engine->registerObjectType("EntitySystem", 0, asOBJ_REF);
	engine->registerObjectBehaviour("EntitySystem", asBEHAVE_ADDREF, "void f()", asMETHOD(System, dummy), asCALL_THISCALL);
	engine->registerObjectBehaviour("EntitySystem", asBEHAVE_RELEASE, "void f()", asMETHOD(System, dummy), asCALL_THISCALL);

	engine->registerObjectMethod("EntitySystem", "Entity@ createEntity()", asMETHOD(System, createEntity), asCALL_THISCALL);
	engine->registerObjectMethod("EntitySystem", "void destroyEntity(Entity@)", asMETHOD(System, destroyEntity), asCALL_THISCALL);

	engine->registerObjectMethod("EntitySystem", "CameraComponent@ get_cameraComponent()", asMETHOD(System, cameraComponent), asCALL_THISCALL);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
